63. 当心字符串连接引起的性能问题

  字符串连接操作符 (+) 是将几个字符串组合成一个字符串的简便方法。对于生成单行输出或构造一个小的、固定大小的对象的字符串表示形式,它是可以的,但是它不能伸缩。使用 字符串串联运算符重复串联 n 个字符串需要 n 的平方级时间。 这是字符串不可变这一事实导致的结果(详见第 17 条)。当连接两个字符串时,将复制这两个字符串的内容。

例如,考虑这个方法,它通过将每个账单项目重复连接到一行来构造账单语句的字符串表示:

  1. // Inappropriate use of string concatenation - Performs poorly!
  2. public String statement() {
  3. String result = "";
  4. for (int i = 0; i < numItems(); i++)
  5. result += lineForItem(i); // String concatenation
  6. return result;
  7. }

  如果项的数量很大,则该方法的性能非常糟糕。要获得能接受的性能,请使用 StringBuilder 代替 String 来存储正在构建的语句:

  1. public String statement() {
  2. StringBuilder b = new StringBuilder(numItems() * LINE_WIDTH);
  3. for (int i = 0; i < numItems(); i++)
  4. b.append(lineForItem(i));
  5. return b.toString();
  6. }

  自 Java 6 以来,为了使字符串连接更快,已经做了大量工作,但是这两个方法在性能上的差异仍然很大:如果 numItems 返回 100,lineForItem 返回 80 个字符串,那么第二个方法在我的机器上运行的速度是第一个方法的 6.5 倍。由于第一种方法在项目数量上是平方级的,而第二种方法是线性的,所以随着项目数量的增加,性能差异会变得越来越大。注意,第二个方法预先分配了一个足够大的 StringBuilder 来保存整个结果,从而消除了自动增长的需要。即使使用默认大小的 StringBuilder,它仍然比第一个方法快 5.5 倍。

  道理很简单:不要使用字符串连接操作符合并多个字符串,除非性能无关紧要。否则使用 StringBuilder 的 append 方法。或者,使用字符数组,再或者一次只处理一个字符串,而不是组合它们。